//
// Authors:
//   Atsushi Enomoto
//
// Copyright 2007 Novell (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if NET_2_0

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace System.Xml.Linq
{
	public class XDocument : XContainer
	{
		XDeclaration xmldecl;

		public XDocument()
		{
		}

		public XDocument(params object[] content)
		{
			Add(content);
		}

		public XDocument(XDeclaration declaration, params object[] content)
		{
			Declaration = declaration;
			Add(content);
		}

		public XDocument(XDocument other)
		{
			foreach (object o in other.Nodes())
				Add(XUtil.Clone(o));
		}

		public XDeclaration Declaration
		{
			get { return xmldecl; }
			set { xmldecl = value; }
		}

		public XDocumentType DocumentType
		{
			get
			{
				foreach (object o in Nodes())
					if (o is XDocumentType)
						return (XDocumentType)o;
				return null;
			}
		}

		public override XmlNodeType NodeType
		{
			get { return XmlNodeType.Document; }
		}

		public XElement Root
		{
			get
			{
				foreach (object o in Nodes())
					if (o is XElement)
						return (XElement)o;
				return null;
			}
		}

		public static XDocument Load(string uri)
		{
			return Load(uri, LoadOptions.None);
		}

		public static XDocument Load(string uri, LoadOptions options)
		{
			XmlReaderSettings s = new XmlReaderSettings();
			s.ProhibitDtd = false; // see XNodeNavigatorTest.MoveToId().
			s.IgnoreWhitespace = (options & LoadOptions.PreserveWhitespace) == 0;
			using (XmlReader r = XmlReader.Create(uri, s))
			{
				return LoadCore(r, options);
			}
		}

		public static XDocument Load(Stream stream)
		{
			return Load(new StreamReader(stream), LoadOptions.None);
		}

		public static XDocument Load(Stream stream, LoadOptions options)
		{
			return Load(new StreamReader(stream), options);
		}

		public static XDocument Load(TextReader textReader)
		{
			return Load(textReader, LoadOptions.None);
		}

		public static XDocument Load(TextReader textReader, LoadOptions options)
		{
			XmlReaderSettings s = new XmlReaderSettings();
			s.ProhibitDtd = false; // see XNodeNavigatorTest.MoveToId().
			s.IgnoreWhitespace = (options & LoadOptions.PreserveWhitespace) == 0;
			using (XmlReader r = XmlReader.Create(textReader, s))
			{
				return LoadCore(r, options);
			}
		}

		public static XDocument Load(XmlReader reader)
		{
			return Load(reader, LoadOptions.None);
		}

		public static XDocument Load(XmlReader reader, LoadOptions options)
		{
			XmlReaderSettings s = reader.Settings != null ? reader.Settings.Clone() : new XmlReaderSettings();
			s.IgnoreWhitespace = (options & LoadOptions.PreserveWhitespace) == 0;
			using (XmlReader r = XmlReader.Create(reader, s))
			{
				return LoadCore(r, options);
			}
		}

		static XDocument LoadCore(XmlReader reader, LoadOptions options)
		{
			XDocument doc = new XDocument();
			doc.ReadContent(reader, options);
			return doc;
		}

		void ReadContent(XmlReader reader, LoadOptions options)
		{
			if (reader.ReadState == ReadState.Initial)
				reader.Read();
			this.FillLineInfoAndBaseUri(reader, options);
			if (reader.NodeType == XmlNodeType.XmlDeclaration)
			{
				Declaration = new XDeclaration(
					reader.GetAttribute("version"),
					reader.GetAttribute("encoding"),
					reader.GetAttribute("standalone"));
				reader.Read();
			}
			ReadContentFrom(reader, options);
			if (Root == null)
				throw new InvalidOperationException("The document element is missing.");
		}

		static void ValidateWhitespace(string s)
		{
			for (int i = 0; i < s.Length; i++)
				switch (s[i])
				{
					case ' ':
					case '\t':
					case '\n':
					case '\r':
						continue;
					default:
						throw new ArgumentException("Non-whitespace text appears directly in the document.");
				}
		}

		public static XDocument Parse(string text)
		{
			return Parse(text, LoadOptions.None);
		}

		public static XDocument Parse(string text, LoadOptions options)
		{
			return Load(new StringReader(text), options);
		}

		public void Save(string fileName)
		{
			Save(fileName, SaveOptions.None);
		}

		public void Save(string fileName, SaveOptions options)
		{
			XmlWriterSettings s = new XmlWriterSettings();
			if ((options & SaveOptions.DisableFormatting) == SaveOptions.None)
				s.Indent = true;
#if NET_4_0
			if ((options & SaveOptions.OmitDuplicateNamespaces) == SaveOptions.OmitDuplicateNamespaces)
				s.NamespaceHandling |= NamespaceHandling.OmitDuplicates;
#endif

			using (XmlWriter w = XmlWriter.Create(fileName, s))
			{
				Save(w);
			}
		}

		public void Save(TextWriter textWriter)
		{
			Save(textWriter, SaveOptions.None);
		}

		public void Save(TextWriter textWriter, SaveOptions options)
		{
			XmlWriterSettings s = new XmlWriterSettings();
			if ((options & SaveOptions.DisableFormatting) == SaveOptions.None)
				s.Indent = true;
#if NET_4_0
			if ((options & SaveOptions.OmitDuplicateNamespaces) == SaveOptions.OmitDuplicateNamespaces)
				s.NamespaceHandling |= NamespaceHandling.OmitDuplicates;
#endif
			using (XmlWriter w = XmlWriter.Create(textWriter, s))
			{
				Save(w);
			}
		}

		public void Save(XmlWriter writer)
		{
			WriteTo(writer);
		}

		public override void WriteTo(XmlWriter writer)
		{
			if (xmldecl != null && xmldecl.Standalone != null)
				writer.WriteStartDocument(xmldecl.Standalone == "yes");
			else
				writer.WriteStartDocument();
			foreach (XNode node in Nodes())
				node.WriteTo(writer);
		}

		internal override bool OnAddingObject(object obj, bool rejectAttribute, XNode refNode, bool addFirst)
		{
			VerifyAddedNode(obj, addFirst);
			return false;
		}

		void VerifyAddedNode(object node, bool addFirst)
		{
			if (node == null)
				throw new InvalidOperationException("Only a node is allowed here");

			if (node is string)
				ValidateWhitespace((string)node);
			if (node is XText)
				ValidateWhitespace(((XText)node).Value);
			else if (node is XDocumentType)
			{
				if (DocumentType != null)
					throw new InvalidOperationException("There already is another document type declaration");
				if (Root != null && !addFirst)
					throw new InvalidOperationException("A document type cannot be added after the document element");
			}
			else if (node is XElement)
			{
				if (Root != null)
					throw new InvalidOperationException("There already is another document element");
				if (DocumentType != null && addFirst)
					throw new InvalidOperationException("An element cannot be added before the document type declaration");
			}
		}
#if NET_4_0
		public void Save (Stream stream)
		{
			Save (stream, SaveOptions.None);
		}

		public void Save (Stream stream, SaveOptions options)
		{
			XmlWriterSettings s = new XmlWriterSettings ();
			if ((options & SaveOptions.DisableFormatting) == SaveOptions.None)
				s.Indent = true;
			if ((options & SaveOptions.OmitDuplicateNamespaces) == SaveOptions.OmitDuplicateNamespaces)
				s.NamespaceHandling |= NamespaceHandling.OmitDuplicates;
	
			using (var writer = XmlWriter.Create (stream, s)){
				Save (writer);
			}
		}

#endif
	}
}

#endif